package cn.xeblog.plugin.action;

import cn.hutool.core.util.StrUtil;
import cn.xeblog.plugin.entity.TextRender;
import cn.xeblog.plugin.enums.Command;
import cn.xeblog.plugin.enums.Style;
import cn.xeblog.plugin.listener.MainWindowInitializedEventListener;
import cn.xeblog.plugin.mode.ModeContext;
import cn.xeblog.plugin.ui.MainWindow;
import com.intellij.ide.BrowserUtil;

import java.awt.Component;
import java.awt.Cursor;
import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.StringSelection;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollBar;
import javax.swing.JScrollPane;
import javax.swing.JTextPane;
import javax.swing.SwingUtilities;
import javax.swing.border.TitledBorder;
import javax.swing.text.AbstractDocument;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.BoxView;
import javax.swing.text.ComponentView;
import javax.swing.text.Document;
import javax.swing.text.Element;
import javax.swing.text.IconView;
import javax.swing.text.LabelView;
import javax.swing.text.ParagraphView;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyledEditorKit;
import javax.swing.text.View;
import javax.swing.text.ViewFactory;

/**
 * 消息面板行为
 *
 * @author anlingyi
 * @date 2020/6/1
 */
public class ConsoleAction implements MainWindowInitializedEventListener {

    private static final Object LOCK = new Object();

    /**
     * 消息面板（消息区域）
     */
    private static JTextPane console;
    /**
     * 左边面板
     */
    private static JPanel panel;
    /**
     * 消息面板滚动条
     */
    private static JScrollPane consoleScroll;
    /**
     * 是否新的一行(true，换行)
     */
    private static volatile boolean isNewLine;

    @Override
    public void afterInit(MainWindow mainWindow) {
        // 初始化属性
        console = mainWindow.getConsoleTextPane();
        panel = mainWindow.getLeftPanel();
        consoleScroll = mainWindow.getConsoleScrollPane();

        // 设置行为编辑器
        console.setEditorKit(new WarpEditorKit());

        // 将给定属性应用于段落
        SimpleAttributeSet simpleAttributeSet = new SimpleAttributeSet();
        // 设置行间距
        StyleConstants.setLineSpacing(simpleAttributeSet, 0.2f);
        console.setParagraphAttributes(simpleAttributeSet, false);

        // 绑定弹出菜单框
        bindPopupMenu();
    }

    /**
     * 重新加载ui
     */
    public static void updateUI() {
        SwingUtilities.invokeLater(() -> console.updateUI());
    }

    /**
     * 渲染文本
     *
     * @param list 文本集
     */
    public static void renderText(List<TextRender> list) {
        for (TextRender textRender : list) {
            renderText(textRender.getText(), textRender.getStyle());
        }
    }

    /**
     * 渲染文本
     *
     * @param text 文本
     */
    public static void renderText(String text) {
        renderText(text, Style.DEFAULT);
    }

    /**
     * 渲染文本
     *
     * @param text  文本
     * @param style 样式
     */
    public static void renderText(String text, Style style) {
        atomicExec(() -> {
            // 换行
            if (isNewLine) {
                ModeContext.getMode().renderTextBefore(text);
            }

            // 渲染
            render(text, style.get());
            
            // 设置换行标识
            isNewLine = text.endsWith("\n");
        });
    }

    /**
     * 显示普通的消息
     *
     * @param msg 消息
     */
    public static void showSimpleMsg(String msg) {
        renderText(msg + "\n", Style.DEFAULT);
    }

    /**
     * 渲染
     *
     * @param content      内容
     * @param attributeSet 样式属性集
     */
    public static void render(String content, AttributeSet attributeSet) {
        atomicExec(() -> {
            Document document = console.getDocument();
            try {
                // 插入文本
                document.insertString(document.getLength(), content, attributeSet);
                if (document.getLength() > 10000) {
                    document.remove(0, 2000);
                    document.insertString(0, "...", Style.DEFAULT.get());
                }
            } catch (BadLocationException e) {
                e.printStackTrace();
            }
            // 当输入消息或命令时，如果此时控制台在最底部，那么控制台滚动条自动滚动到底端
            gotoConsoleLow();
        });
    }

    /**
     * 渲染图片Label
     *
     * @param label 标签
     */
    public static void renderImageLabel(JLabel label) {
        atomicExec(() -> {
            renderText("[");
            renderComponent(label);
            renderText("]\n");
        });
    }

    /**
     * 渲染(url)
     *
     * @param title 标题
     * @param url   url路径
     */
    public static void renderUrl(String title, String url) {
        JLabel label = new JLabel(title);
        label.setAlignmentY(0.85f);
        label.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
        label.setForeground(StyleConstants.getForeground(Style.DEFAULT.get()));
        // 添加监听，点击进行浏览器浏览
        label.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                BrowserUtil.browse(url);
            }
        });
        renderComponent(label);
    }

    /**
     * 渲染组件(例如:图片/URL组件)
     *
     * @param component 组件
     */
    public static void renderComponent(Component component) {
        JScrollBar verticalScrollBar = consoleScroll.getVerticalScrollBar();
        int beforeScrollVal = verticalScrollBar.getValue();
        // 更新光标至顶部
        updateCaretPosition(-1);
        // 插入组件
        console.insertComponent(component);
        // 设置
        SwingUtilities.invokeLater(() -> verticalScrollBar.setValue(beforeScrollVal));
    }

    /**
     * 清空控制台的消息
     */
    public static void clean() {
        console.setText("");
    }

    /**
     * 设置控制台标题
     *
     * @param title 标题
     */
    public static void setConsoleTitle(String title) {
        synchronized (panel) {
            ((TitledBorder) panel.getBorder()).setTitle(title);
            panel.updateUI();
        }
    }

    /**
     * 当输入消息或命令时，如果此时控制台在最底部，那么控制台滚动条自动滚动到底端
     * <p>note: 在顶部时，不会自动滚动到底端
     */
    public static void gotoConsoleLow() {
        gotoConsoleLow(false);
    }

    /**
     * 获取控制台
     *
     * @param forced 是否强制光标永远在最后一行，即滚动条随着消息增加一直在底部
     */
    public static void gotoConsoleLow(boolean forced) {
        if (!forced) {
            JScrollBar verticalScrollBar = consoleScroll.getVerticalScrollBar();
            // 当滚动条在底部，此时为false,当滚动条在底部在顶部，此时为true
            if (verticalScrollBar.getValue() + 20 < verticalScrollBar.getMaximum() - verticalScrollBar.getHeight()) {
                return;
            }
        }

        // 更新光标至最后一行
        updateCaretPosition(-1);
    }

    /**
     * 显示错误消息
     */
    public static void showErrorMsg() {
        ConsoleAction.showSimpleMsg("输入的命令有误！帮助命令：" + Command.HELP.getCommand());
    }

    /**
     * 显示登录消息
     */
    public static void showLoginMsg() {
        ConsoleAction.showSimpleMsg("请先登录！登录命令：" + Command.LOGIN.getCommand() + "，帮助命令：" + Command.HELP.getCommand());
    }

    /**
     * 绑定弹出的菜单框，即在消息区域内，右键显示的菜单操作
     */
    private static void bindPopupMenu() {
        // 设置弹出菜单组件
        JPopupMenu jPopupMenu = new JPopupMenu("右键菜单");
        console.setComponentPopupMenu(jPopupMenu);

        // 复制内容操作
        JMenuItem copyItem = new JMenuItem("复制内容");
        copyItem.addActionListener(ev -> {
            // 在消息区域选中的文本
            String selectedText = console.getSelectedText();
            if (StrUtil.isBlank(selectedText)) {
                return;
            }

            // 获取系统剪贴板
            Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
            // 封装文本内容
            StringSelection contents = new StringSelection(selectedText);
            // 把文本内容设置到系统剪贴板
            clipboard.setContents(contents, null);
        });

        // 百度搜索操作
        JMenuItem searchItem = new JMenuItem("百度搜索");
        searchItem.addActionListener(ev -> {
            // 在消息区域选中的文本
            String selectedText = console.getSelectedText();
            if (StrUtil.isBlank(selectedText)) {
                return;
            }

            // 将选中的内容打开浏览器并在百度中检索
            BrowserUtil.browse("https://www.baidu.com/s?wd=" + selectedText);
        });

        JMenuItem openUrlItem = new JMenuItem("打开网址");
        openUrlItem.addActionListener(ev -> {
            // 在消息区域选中的文本
            String selectedText = console.getSelectedText();
            if (StrUtil.isBlank(selectedText)) {
                return;
            }

            // 设置协议前缀，拼接后打开浏览器并访问
            if (!selectedText.startsWith("http")) {
                selectedText = "https://" + selectedText;
            }
            BrowserUtil.browse(selectedText);
        });

        // 加入菜单
        jPopupMenu.add(copyItem);
        jPopupMenu.add(searchItem);
        jPopupMenu.add(openUrlItem);

        // 加入菜单分隔符
        jPopupMenu.addSeparator();

        Map<String, Command> commandMap = new LinkedHashMap<>();
        commandMap.put("快速登录", Command.LOGIN);
        commandMap.put("加入游戏", Command.JOIN);
        commandMap.put("清者自清", Command.CLEAN);
        commandMap.put("需要帮助！", Command.HELP);
        commandMap.put("退！退！退！", Command.LOGOUT);

        // 加入菜单
        commandMap.forEach((k, v) -> jPopupMenu.add(k).addActionListener(l -> {
            // 显示命令消息
            ConsoleAction.showSimpleMsg(v.getCommand());
            v.exec();
        }));
    }

    /**
     * 显示系统消息
     *
     * @param time 日期
     * @param msg  消息
     */
    public static void showSystemMsg(String time, String msg) {
        ConsoleAction.renderText(String.format("[%s] 系统消息：%s\n", time, msg), Style.SYSTEM_MSG);
    }

    /**
     * 更新光标至指定位置
     *
     * @param position 位置,-1表示最后一行
     */
    private static void updateCaretPosition(int position) {
        atomicExec(() -> {
            //位置处理
            int pos = position;
            if (pos == -1) {
                pos = console.getDocument().getLength();
            }
            // 光标定位
            // 光标定位
            console.setCaretPosition(pos);
        });
    }

    /**
     * 编辑器工具包（样式化文本，提供编辑样式化文本的最小操作集合）
     * <p>
     * 针对Bug  JTextPane中当输入较长的英文无空格时，会认为这是一个单词，并且自动换行失效，导致JTextPane的实际宽度变大出现滚动条,通过重写编辑器工具包来控制这个bug
     * </p>
     * <p>内部类---以下内部类全都用于实现自动强制折行</p>
     */
    public static class WarpEditorKit extends StyledEditorKit {


        /**
         * 默认的视图工厂
         */
        private ViewFactory defaultFactory = new WarpColumnFactory();

        @Override
        public ViewFactory getViewFactory() {
            return defaultFactory;
        }

        /**
         * 视图工厂，
         * <p>
         * 与StyledEditorKit#defaultFactory相比:
         * <pre>
         * public View create(Element elem) {
         *   ...
         *    if (kind.equals(AbstractDocument.ContentElementName)) {
         *      return new LabelView(elem);
         *   ...
         * }
         * </pre>
         * 只是针对AbstractDocument.ContentElementName重写了LabelView实现
         */
        private class WarpColumnFactory implements ViewFactory {

            @Override
            public View create(Element elem) {
                String kind = elem.getName();
                if (kind != null) {
                    switch (kind) {
                        // 内容
                        case AbstractDocument.ContentElementName:
                            return new WarpLabelView(elem);
                        // 段落
                        case AbstractDocument.ParagraphElementName:
                            return new ParagraphView(elem);
                        // 节(行/段)
                        case AbstractDocument.SectionElementName:
                            return new BoxView(elem, View.Y_AXIS);
                        // 组件
                        case StyleConstants.ComponentElementName:
                            return new ComponentView(elem);
                        // 图标
                        case StyleConstants.IconElementName:
                            return new IconView(elem);
                    }
                }

                return new LabelView(elem);
            }
        }

        /**
         * 重写LabelView，主要是重写LabelView#getMinimumSpan方法中的针对View.X_AXIS操作
         */
        private class WarpLabelView extends LabelView {

            public WarpLabelView(Element elem) {
                super(elem);
            }

            @Override
            public float getMinimumSpan(int axis) {
                // 确定视图指定轴的最小宽度
                switch (axis) {
                    case View.X_AXIS:
                        return 0;
                    case View.Y_AXIS:
                        return super.getMinimumSpan(axis);
                    default:
                        throw new IllegalArgumentException("Invalid axis: " + axis);
                }
            }
        }

    }

    /**
     * 原子执行
     *
     * @param runnable 任务
     */
    public static void atomicExec(Runnable runnable) {
        synchronized (LOCK) {
            runnable.run();
        }
    }

}
